/*
PearReview - The peer review assistant.
Copyright (C) 2009  Dimitrij Pankratz, Anton Musichin
http://www.pearreview.com, info@pearreview.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package com.pearreview.ui.datepicker;

import javax.swing.*;
import javax.swing.Timer;

import com.pearreview.ui.DateSpinner;
import com.pearreview.ui.FormLabel;

import java.awt.*;
import java.util.*;
import java.text.SimpleDateFormat;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * Displays a date picker to a user.
 *
 */
public class DatePicker extends JPopupMenu {
	
	private static final long serialVersionUID = 8538889415622649609L;
	
	private SimpleDateFormat month = new SimpleDateFormat("MMMM");
	private SimpleDateFormat year = new SimpleDateFormat("yyyy");
	
	private Date date;
	private ActionListener dateAction;
	
	private CalendarButtonsPanel lastCalendarButtons;
	private JLabel yearSpin;
	private JLabel monthLabel;
	private boolean sliding = false;
	
	public DatePicker(final DateSpinner field) {
		super();
		
		initDatePicker(field);
	}
	
	public Date getDate() {
		return date;
	}
	
	public void setDate(Date date) {
		this.date = date;
	}
	
	private void initDatePicker(final DateSpinner field) {
		setFont(new Font("Tahoma", Font.PLAIN, 12));
		
		dateAction = new ActionListener() { 
			@Override
			public void actionPerformed(ActionEvent e) {
				Calendar today = Calendar.getInstance();
				today.clear();
				today.setTime(date);
				
				DatePickerButton btn = (DatePickerButton) e.getSource();
				today.set(Calendar.DAY_OF_MONTH, btn.getDay());
				field.setDate(today.getTime());
				
				setVisible(false);
			}
		};
		if (field.getDate() != null) {
			date = field.getDate();
		} else {
			date = new Date();
		}
		
		ActionListener monthBackAction = new ActionListener() { 
			@Override
			public void actionPerformed(ActionEvent e) {
				if (!sliding) {
					Calendar today = Calendar.getInstance();
					today.setTime(date);
					today.add(Calendar.MONTH, -1);
					date = today.getTime();
					slideLeft();
					updateCalendar();
				}
			}
		}; 
		
		ActionListener monthNextAction = new ActionListener() { 
			@Override
			public void actionPerformed(ActionEvent e) {
				if (!sliding) {
					Calendar today = Calendar.getInstance();
					today.setTime(date);
					today.add(Calendar.MONTH, +1);
					date = today.getTime();
					slideRight();
					updateCalendar();
				}
			}
		};
		
		final JPanel labelPanel = new JPanel();
		labelPanel.setLayout(new BorderLayout());
		labelPanel.setOpaque(false);
		
		JButton monthBackBtn = new JButton();
		monthBackBtn.setIcon(getResourceIcon("prev_month.png"));
		monthBackBtn.setOpaque(false);
		monthBackBtn.setFocusable(false);
		monthBackBtn.addActionListener(monthBackAction);
		labelPanel.add(monthBackBtn, BorderLayout.WEST); 
		
		//label for months
		Calendar calendar = Calendar.getInstance();
		calendar.clear();
		calendar.setTime(date);
		monthLabel = new JLabel();
		monthLabel.setHorizontalAlignment(SwingConstants.CENTER);
		monthLabel.setText(month.format(calendar.getTime()));
		monthLabel.setFont(getFont());
		labelPanel.add(monthLabel, BorderLayout.CENTER);
		
		
		JButton monthNextBtn = new JButton();
		monthNextBtn.setIcon(getResourceIcon("next_month.png"));
		monthNextBtn.setOpaque(false);
		monthNextBtn.setFocusable(false);
		monthNextBtn.addActionListener(monthNextAction );
		labelPanel.add(monthNextBtn, BorderLayout.EAST); 

		yearSpin = new FormLabel();
		yearSpin.setText(year.format(calendar.getTime()));
		yearSpin.setFocusable(false);
		yearSpin.setHorizontalAlignment(SwingConstants.CENTER);

		labelPanel.add(yearSpin, BorderLayout.SOUTH);
		
		add(labelPanel, BorderLayout.NORTH);
		
		updateCalendar();
	}
	
	private CalendarButtonsPanel newCalendarButtons(Date date) {
		return newCalendarButtons(date, null);
	}
	
	private CalendarButtonsPanel newCalendarButtons(Date date, 
			CalendarButtonsPanel panel) {
		if(panel == null) {
			panel = new CalendarButtonsPanel(date);
			panel.setFont(getFont());
			panel.setClickAction(dateAction);
		} else {
			panel.setDate(date);
		}
		
		return panel;
	}
	
	/**
	 * All values of the month, year and days will be updated and
	 * an animation will be performed.
	 */
	private void updateCalendar() {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		monthLabel.setText(month.format(calendar.getTime()));
		
		yearSpin.setText(year.format(calendar.getTime()));
		
		if(!sliding) {
			if(lastCalendarButtons == null) {
				lastCalendarButtons = newCalendarButtons(date);
				add(lastCalendarButtons, BorderLayout.CENTER);
			}
			
			//lastCalendarButtons = 
			//		newCalendarButtons(date, lastCalendarButtons);
		}
	}
	
	/**
	 * Animates a slide of the calendar buttons to the left.
	 * Locks changes while sliding.
	 */
	private void slideLeft() {
		slide(true);
	}
	
	/**
	 * Animates a slide of the calendar buttons to the right.
	 * Locks changes while sliding.
	 */
	private void slideRight() {
		slide(false);
	}
	
	private void slide(final boolean b) {
		if(sliding) {
			return;
		}
		
		sliding = true;
		
		ActionListener ae = new ActionListener() {
			final int STEP = 5;
			boolean oldCalendar = true;
			long t = System.nanoTime();
			final int x = lastCalendarButtons.getX();
			Rectangle r = new Rectangle(
					lastCalendarButtons.getX(), 
					lastCalendarButtons.getY(), 
					lastCalendarButtons.getWidth(),
					lastCalendarButtons.getHeight());
			@Override
			public void actionPerformed(ActionEvent e) {
				if(System.nanoTime() - t > 1500000L) {
					t = System.nanoTime();
					
					r.x += b ? -STEP : +STEP;
					if(oldCalendar && r.x >= getWidth()) {
						lastCalendarButtons = 
								newCalendarButtons(date, lastCalendarButtons);
						oldCalendar = false;
						r.x = - r.width;
					} else if(oldCalendar && r.x + r.width <= 0) {
						lastCalendarButtons = 
								newCalendarButtons(date, lastCalendarButtons);
						oldCalendar = false;
						r.x = getWidth();
					} else if(!oldCalendar && Math.abs(r.x - x) < STEP) {
						r.x = x;
						lastCalendarButtons.setBounds(r);
						((Timer)e.getSource()).stop();
						sliding = false;
						return;
					}
					
					lastCalendarButtons.setBounds(r);
				}
			}
		};
		
		new Timer(0, ae).start();
	}
	
	@Override
	protected void paintComponent(Graphics g) {
		
		Graphics2D g2 = (Graphics2D) g;
		super.paintComponent(g);
		GradientPaint p;
		p = new GradientPaint(0, 0, new Color(0xd6d6d6),
				0, getHeight(), new Color(0xa9a9a9));
		Paint oldPaint = g2.getPaint();
		g2.setPaint(p);
		g2.fillRect(0, 0, getWidth(), getHeight());
		g2.setPaint(oldPaint);
	}
	
	/**
	 * Returns a resource icon
	 * @param name name of icon
	 * @return
	 */
	private ImageIcon getResourceIcon(String name) {
		return new ImageIcon(getClass().getResource("images/" + name));
	}
	
	private class CalendarButtonsPanel extends JPanel {
		private static final long serialVersionUID = 8281095945940292752L;
		
		private final int H_GAP = 3;
		private final int V_GAP = H_GAP;
		private final int TOP_GAP = 10;
		private final int BUTTOM_GAP = 20;
		private final int LEFT_RIGHT_GAP = BUTTOM_GAP;
		
		private ActionListener clickAction;
		
		private SimpleDateFormat day = new SimpleDateFormat("d");
		private SimpleDateFormat dayT = new SimpleDateFormat("E");
		
		private final DatePickerButton buttons[][] = new DatePickerButton[7][6];
		
		public CalendarButtonsPanel(Date date) {
			setOpaque(false);
			
			GroupLayout layout = new GroupLayout(this);
			setLayout(layout);
			GroupLayout.Group vGroup = layout.createSequentialGroup();
			GroupLayout.Group hGroup = layout.createSequentialGroup();
			layout.setVerticalGroup(vGroup);
			layout.setHorizontalGroup(hGroup);
			
			
			GroupLayout.ParallelGroup dayGroup[] = 
					new GroupLayout.ParallelGroup[7];
			GroupLayout.ParallelGroup weekGroup[] = 
					new GroupLayout.ParallelGroup[7];
			weekGroup[0] = layout.createParallelGroup();
			vGroup.addGap(TOP_GAP);
			vGroup.addGroup(weekGroup[0]);
			
			Calendar dayCal = Calendar.getInstance();
			dayCal.setTime(date);
			dayCal.set(Calendar.DATE,1);
			dayCal.add(Calendar.DATE,-dayCal.get(Calendar.DAY_OF_WEEK)+1);
			
			hGroup.addGap(LEFT_RIGHT_GAP);
			for(int day = 0 ; day < 7 ; day++) {
				JLabel currentLabel = new JLabel(dayT.format(dayCal.getTime()));
				currentLabel.setFont(getFont());
				
				dayCal.add(Calendar.DATE,+1);
				
				dayGroup[day] = layout.createParallelGroup();
				hGroup.addGap(H_GAP);
				hGroup.addGroup(dayGroup[day]);
				
				dayGroup[day].addComponent(currentLabel,
						GroupLayout.Alignment.CENTER);
				weekGroup[0].addComponent(currentLabel);
			}
			hGroup.addGap(H_GAP);
			hGroup.addGap(LEFT_RIGHT_GAP);
			
			for(int w = 0; w < 6; w++) {
				weekGroup[w + 1] = layout.createParallelGroup();
				vGroup.addGap(V_GAP);
				vGroup.addGroup(weekGroup[w + 1]);
				
				for(int d = 0; d < 7; d++) {
					buttons[d][w] = new DatePickerButton();
					buttons[d][w].setFocusable(false);
					buttons[d][w].setPlaceHolder(true);
					dayGroup[d].addComponent(buttons[d][w]);
					weekGroup[w + 1].addComponent(buttons[d][w]);
				 }
			}
			vGroup.addGap(H_GAP);
			vGroup.addGap(BUTTOM_GAP);
			
			setDate(date);
		}
		
		public void setDate(Date date) {
			Calendar cal = Calendar.getInstance();
			cal.setTime(date);
			cal.set(Calendar.DATE,1);
			cal.add(Calendar.DATE,-cal.get(Calendar.DAY_OF_WEEK)+1);
			
			Calendar today = Calendar.getInstance();
			
			Calendar current = Calendar.getInstance();
			current.setTime(date);
			
			//draw days
			Holidays holidays = null;
			if(Locale.getDefault().getCountry().equals("DE")) {
				holidays = new GermanHolidays(current.get(Calendar.YEAR));
			}
			
			boolean holder;
			for(int w = 0; w < 6; w++) {
				for(int d = 0; d < 7; d++) {
					holder = cal.get(Calendar.MONTH) 
							!= current.get(Calendar.MONTH);
					buttons[d][w].setPlaceHolder(holder);
					
					if(!holder) {
						int iDay = Integer.parseInt(day.format(cal.getTime()));
						buttons[d][w].setDay(iDay);
						boolean isToday = cal.get(Calendar.MONTH) == 
								today.get(Calendar.MONTH) &&
								cal.get(Calendar.DAY_OF_MONTH) == 
								today.get(Calendar.DAY_OF_MONTH) &&
								cal.get(Calendar.YEAR) == 
								today.get(Calendar.YEAR);
						buttons[d][w].setToday(isToday);
						
						//check for some holidays for current date
						if(holidays != null) {
							buttons[d][w].setCalendarDay(holidays.getDay(cal));
						} else {
							buttons[d][w].setCalendarDay(null);
						}
						
						//if Sunday set button to holiday
						buttons[d][w].setHoliday(
								buttons[d][w].isHoliday() || d == 0);
					}
					
					cal.add(Calendar.DATE, 1);
				}
			}
		}
		
		/**
		 * Returns the action listener of button for mouse released event.
		 * 
		 * @return
		 */
		public ActionListener getClickAction() {
			return clickAction;
		}


		/**
		 * Sets the action listener to the button, which will be performed,
		 * if mouse is released over this button.
		 * 
		 * @param clickAction
		 */
		public void setClickAction(ActionListener clickAction) {
			this.clickAction = clickAction;
			
			for(DatePickerButton[] btns: buttons) {
				for(DatePickerButton btn: btns) {
					btn.setClickAction(getClickAction());
				}
			}
		}
	}
}